/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd.model;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.KeyStore;
import java.security.SecureRandom;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.zhiqim.httpd.HttpdConstants;
import org.zhiqim.httpd.nio.HttpNioSslConnection;
import org.zhiqim.kernel.config.Group;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Validates;

/**
 * SSL参数配置和管理
 *
 * @version v1.0.0 @author zouzhigang 2018-6-30 新建与整理
 */
public class HttpSslConfig implements HttpdConstants
{
    //两项必须（服务端公私钥）
    private String keystorePath;            // = "./conf/keystore.jks";
    private String keystorePass;            // = "123456";
    
    //三项根据要求可选（CA信任公钥）
    private String truststorePath;          // = "./conf/truststore.jks";
    private String truststorePass;          // = "123456";
    private boolean clientAuth;             //是否需要客户端证验证（双向验证，客户端也要导入证书才行）
    
    public void initParam(Group group)
    {
        //1.SSL参数（五项，其中Keystore两项必须）
        keystorePath = group.getString(_KEY_STORE_PATH_);
        keystorePass = group.getString(_KEY_STORE_PASS_);
        
        Asserts.as(Validates.isNotEmpty(keystorePath)?null:"配置HTTPS时要求必须配置[keystorePath]不能为空");
        Asserts.as(Validates.isNotEmpty(keystorePass)?null:"配置HTTPS时要求必须配置[keystorePass]不能为空");
        
        truststorePath = group.getString(_TRUST_STORE_PATH_);
        truststorePass = group.getString(_TRUST_STORE_PASS_);
        
        Asserts.as((Validates.isNotEmpty(truststorePath)?Validates.isNotEmpty(truststorePass):true)?null:"配置HTTPS时如要配置了[truststorePath]则[truststorePass]不能为空");
        
        clientAuth = group.isTrue(_CLIENT_AUTH_);
    }
    
    /******************************************************************************/
    //获取SSL创建的SSLContext&SSLServerSocketFactory
    /******************************************************************************/

    /** 创建SSL工厂 */
    public SSLServerSocketFactory createFactory() throws Exception
    {
        return createSSLContext().getServerSocketFactory();
    }
    
    /** 创建SSL环境 */
    public SSLContext createSSLContext() throws Exception
    {
        SSLContext sslContext = SSLContext.getInstance(SSL_PROTOCOL);
        
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(SSL_ALGORITHM);
        KeyStore keyStore = KeyStore.getInstance(SSL_KEYSTORE_TYPE);
        
        FileInputStream kfis = new FileInputStream(keystorePath);
        keyStore.load(kfis, keystorePass.toCharArray());
        keyManagerFactory.init(keyStore, keystorePass.toCharArray());
        
        TrustManager[] trustManager = null;
        if (Validates.isNotEmptyBlank(truststorePath))
        {//有CA信任证书的创建管理器
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(SSL_ALGORITHM);
            KeyStore trustStore = KeyStore.getInstance(SSL_KEYSTORE_TYPE);
            FileInputStream tfis = new FileInputStream(truststorePath);
            trustStore.load(tfis, truststorePass.toCharArray());
            trustManagerFactory.init(trustStore);
            
            trustManager = trustManagerFactory.getTrustManagers();
        }
        
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManager, new SecureRandom());
        return sslContext;
    }
    
    /******************************************************************************/
    //SSL握手
    /******************************************************************************/

    /** 握手操作 */
    public boolean doHandshake(HttpNioSslConnection conn)
    {
        //1.初始化参数
        SSLEngine sslEngine = conn.getSSLEngine();
        
        SSLSession session = sslEngine.getSession();
        int appBufMax = session.getApplicationBufferSize();
        int netBufMax = session.getPacketBufferSize();
            
        ByteBuffer appBuf = ByteBuffer.allocate(appBufMax + 10);
        ByteBuffer netBuf = ByteBuffer.allocate(netBufMax);
        
        try
        {
            //2.开启握手
            sslEngine.beginHandshake();
            
            //3.握手执行
            return doHandshake(conn, sslEngine, appBuf, netBuf);
        }
        catch(Exception e)
        {//4.握手异常
            return false;
        }
    }
    
    /** 握手执行 */
    private boolean doHandshake(HttpNioSslConnection conn, SSLEngine sslEngine, ByteBuffer appBuf, ByteBuffer netBuf) throws IOException
    {
        HandshakeStatus hsStatus = sslEngine.getHandshakeStatus();
        
        boolean handshakeDone = false;
        while (!handshakeDone) 
        {
            switch (hsStatus)
            {
            case NEED_UNWRAP:
            {//1.客户端请求Hello
                netBuf.clear();
                conn.read(netBuf);
                netBuf.flip();
         
                do
                {
                    sslEngine.unwrap(netBuf, appBuf);
                    hsStatus = doTask(sslEngine);
                }
                while(netBuf.hasRemaining() && hsStatus ==  SSLEngineResult.HandshakeStatus.NEED_UNWRAP);
                break;
            }
            case NEED_WRAP:
            {//2.服务端响应Hello
                netBuf.clear();
                sslEngine.wrap(_HELLO_N_, netBuf);
                netBuf.flip();
                
                conn.write(netBuf);
                hsStatus = doTask(sslEngine);
                break;
            }
            case NEED_TASK:
            {//3.等待任务，当前使用同步处理
                hsStatus = doTask(sslEngine);
                break;
            }
            case NOT_HANDSHAKING:
            {//4.握手成功
                handshakeDone = true;
                break;
            }
            case FINISHED:
            {//5.握手完成
                break;
            }
            }
        }
        
        return handshakeDone;
    }
    
    /** 握手任务 */
    private HandshakeStatus doTask(SSLEngine sslEngine)
    {
        Runnable task;
        while ((task = sslEngine.getDelegatedTask()) != null) 
        {
            task.run();
        }

        return sslEngine.getHandshakeStatus();
    }
    
    /******************************************************************************/
    //获取配置参数值
    /******************************************************************************/

    public String getKeystorePath()
    {
        return keystorePath;
    }

    public String getKeystorePass()
    {
        return keystorePass;
    }

    public String getTruststorePath()
    {
        return truststorePath;
    }

    public String getTruststorePass()
    {
        return truststorePass;
    }

    public boolean isClientAuth()
    {
        return clientAuth;
    }
}
